import axios from 'axios';
import fs from 'fs';
const cheerio = require('cheerio');

// 接口数据结构定义
interface Movie {
    id: number;
    title: string;
    director: string;
    rating: string;
    summary: string;
}
// 列表页地址列表
let urls: string[] = [
    "https://movie.douban.com/top250?start=0&filter=",
    "https://movie.douban.com/top250?start=25&filter=",
    "https://movie.douban.com/top250?start=50&filter=",
    "https://movie.douban.com/top250?start=75&filter=",
    "https://movie.douban.com/top250?start=100&filter=",
    "https://movie.douban.com/top250?start=125&filter=",
    "https://movie.douban.com/top250?start=150&filter=",
    "https://movie.douban.com/top250?start=175&filter=",
    "https://movie.douban.com/top250?start=200&filter=",
    "https://movie.douban.com/top250?start=225&filter=",
]


// 设置数据写入目录
const dir = './fatchDouban/doubanTop250.json';

// 是否启用cookie
const useCookie = false;

// 手动登录豆瓣网之后，访问目标页面，F12中获取对应的GET页面请求中的Header中的Cookie信息
// 设置登录Cookie等信息
const cookie = '';

// 经测试，总共耗时大概5-6分钟左右，请求10次列表页面，请求250次详情页面
// 不使用并发，因为容易被服务器识别为爬虫，所以使用串行请求

// 主函数
async function fetchDoubanTop250(): Promise<Movie[]> {

    // ID计数器
    var id = 1;
    const allMovies: Movie[] = [];

    try {
        for (const url of urls) {
            // 防止ban IP，随机延迟100ms到200ms之间  
            const delay = 100 + Math.random() * 100;  
            await new Promise(resolve => setTimeout(resolve, delay));

            try {
                const response = await sentUrl(url);
                const html = response.data as string;
                const $ = cheerio.load(html);
                // 解析列表页面的每个电影信息,传入开始的ID  
                const movies = await parse($, id);
                // 每个页面有25部电影，所以每次递增25  
                id += 25;
                // 将解析出的电影信息添加到数组中  
                allMovies.push(...movies);
            } catch (error) {
                throw error;
            }
        }
        // 返回所有电影信息  
        return allMovies;
    } catch (error) {
        console.error('主函数错误:', error);
        throw error;
    }
}

// 解析列表页面信息
async function parse($: any, startId: number): Promise<Movie[]> {
    const movies: Movie[] = [];

    // 获取所有li标签
    const $lis = $('ol > li');

    for (const element of $lis.toArray()) {
        const $li = $(element);
        // 获取电影名称和导演和主演和评分
        const title = $li.find('.title').text();
        const rating = $li.find('.rating_num').text();
        // 去除<br>标签以及后面的内容,处理一下文本信息
        const director = $li.find('div.bd > p').html().split('<br>')[0].replace(/[\n\r\s]+/g, ' ').replace(/&nbsp;/g, ' ').trim();
        // 添加到数组中
        movies.push({ id: startId, title, director, rating, summary: '' });

        // 获取电影详情页链接
        const href = $li.find('.info').find('a').attr('href');
        if (href) {
            // 发送请求获取详情页信息  
            await parse_detail(startId, href, movies);
        } else {
            console.log("详情链接获取异常");
        }
        // 计数器+1
        startId++;
    };

    return movies;
}

// 解析详情页信息
async function parse_detail(id: number, url: string, movies: Movie[]): Promise<Movie[]> {

    const response = await sentUrl(url);
    const html = response.data as string;
    const $ = cheerio.load(html);

    // 获取剧情简介
    const summary: string = $('span[property="v:summary"]').text();
    const movie = movies.find(movie => movie.id === id);
    if (movie) {
        // 去除html标签,去除收尾空格,换行符替换成空格 
        movie.summary = summary.replace(/<[^>]*>?/gm, '')
            .replace(/[\n\r\s]+/g, ' ')
            .trim();
    } else {
        console.error('找不到电影对象,ID: ' + id);
    }
    return movies;
}

// 封装请求函数
function sentUrl(url: string) {
    if(useCookie){
        return axios.get(url, {
            headers: {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36',
                'Cookie': cookie
            }
        });
    }else{
        return axios.get(url, {
            headers: {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36'
            }
        });
    }
}

// 执行主函数并写入文件
// 写入到当前项目的fatchDouban目录下的doubanTop250.json文件
fetchDoubanTop250().then(movies => {
    fs.writeFile(dir, JSON.stringify(movies, null, 4), 'utf8', () => { console.log('写入成功') });
}); 
